package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuAttrValue;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.swing.text.Highlighter;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 李旭
 * @date 2021/11/23 9:32
 * @Description:
 */
@Service
public class ListServiceImpl implements ListService {

    //RedisClient  Redis官方客户端
    //RedisTemplate  Spring官方客户端  底层封装了RedisClient


    //1:   ES官方客户端 (低级别）
/*    @Autowired
    private TransportClient transportClient;
    //2:Spring官方客户端  底层封装了 ES官方客户端 (低级别）
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;*/

    //==================================================

    /**
     * elasticsearch:
     *   rest:
     *    uris: http://192.168.200.130:9200
     */
    //3:ES官方客户端  ES官方客户端（(高级版）
    @Autowired
    private RestHighLevelClient restHighLevelClient;  //查询    性能高  DSL语句
    //4:Spring官方客户端 (高级版）  底层封装了 ES官方客户端（(高级版）
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;//查询  性能相比于上面低一点点
    @Autowired
    private GoodsDao goodsDao;//添加  修改 删除  根据ID查询一个Document
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RedisTemplate redisTemplate;

    //创建索引库
    //mappings映射
    @Override
    public void createIndex() {
        //创建索引库
        elasticsearchRestTemplate.createIndex(Goods.class);
        //mappings映射
        elasticsearchRestTemplate.putMapping(Goods.class);//

        System.out.println(elasticsearchRestTemplate.toString());

     /*   IndexRequest indexRequest = new IndexRequest();
        restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);*/


    }


    //上架 添加商品数据到索引库
    @Override
    public void onSale(Long skuId) {
        Goods goods = new Goods();

        //将Goods中所需要的数据查询出来 并设置到Goods对象中 再进行保存索引

        //1:skuInfo 库存信息
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuInfo.getId());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setTitle(skuInfo.getSkuName());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        //2:时间
        goods.setCreateTime(new Date());
        //3:一二三级分类
        BaseCategoryView baseCategoryView = productFeignClient.getBaseCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(baseCategoryView.getCategory1Id());
        goods.setCategory1Name(baseCategoryView.getCategory1Name());
        goods.setCategory2Id(baseCategoryView.getCategory2Id());
        goods.setCategory2Name(baseCategoryView.getCategory2Name());
        goods.setCategory3Id(baseCategoryView.getCategory3Id());
        goods.setCategory3Name(baseCategoryView.getCategory3Name());
        //4:品牌
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(skuInfo.getTmId());
        goods.setTmId(baseTrademark.getId());
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());
        //5:skuInfo与平台属性及值的关联数据
        List<SkuAttrValue> skuAttrValueList = productFeignClient.getSkuAttrValueList(skuId);
        List<SearchAttr> attrsList = skuAttrValueList.stream().map(skuAttrValue -> {
            SearchAttr searchAttr = new SearchAttr();
            searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
            searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
            searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(attrsList);

        //保存索引
        goodsDao.save(goods);

    }
    //下架  删除索引库中商品数据
    @Override
    public void cancelSale(Long skuId) {
        //删除
        goodsDao.deleteById(skuId);

    }
    //商品热度评分  更新操作
    @Override
    public void hotScore(Long skuId, Integer score) {
        String hotScore = "hotScore";
        //1:缓存铺垫  +1 +2 +3 +5 让缓存完成
        //1.1 : 五大数据类型中ZSet类型  有序集合类型
        /**
         *  回顾： ZSet
         *       小明 :   语文 100分  数学 88分  英文 77分
         *
         *   场景：
         *      hotScore:  1  分数  2  分数  3  分数
         */
        Double score1 = redisTemplate.opsForZSet().incrementScore(hotScore, skuId, score);
        if(score1%10 == 0){// 10,20,30,40....
            //2:达到阈值10分 更新一次索引库
            //1:查询索引库中数据
            Optional<Goods> byId = goodsDao.findById(skuId);
            Goods goods = byId.get();
            //2:追加分数
            goods.setHotScore(Math.round(score1));  // 45.67
            //3:更新索引库中的数据
            goodsDao.save(goods);
            System.out.println("当前商品:" + skuId + ":热度评分：" + score1 + ":更新索引库了");
        }
        System.out.println("当前商品:" + skuId + ":热度评分：" + score1);

        //上面的代码性能差 ：
        /**
         * ES: 底层是Lucene （是Apache公司开发的）
         * Lucene ：是开发全文搜索引擎工具包
         *     查询非常快 （亚秒级查询）  查询永远不超过1S
         *     添加、修改 速度非常慢
         * 百度：底层全文检索
         *
         * 新华字典： 就是ES底层原理
         *
         *     目录 10~50页   ：    其它页面 （数据）
         *     晨 -------300页：     直达300页  晨：早上的一束阳光 。。
         *
         *    制作新华字典： 教育局 查询 上下5000年历史  总结
         *
         */

    }


    //开始搜索  大约200行
    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        //1:搜索请求对象
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        //2:开始搜索
        try {
            SearchResponse searchResponse = restHighLevelClient.
                        search(searchRequest, RequestOptions.DEFAULT);
            //3:解析搜索结果
            SearchResponseVo vo = parseSearchResponse(searchResponse);
            //3.1:计算总页数
            //总页数 == (总条数 + 每页数 -1)/每页数
            vo.setPageNo(searchParam.getPageNo());
            vo.setPageSize(searchParam.getPageSize());
            long totalPages = (vo.getTotal() + vo.getPageSize() - 1) / vo.getPageSize();
            vo.setTotalPages(totalPages);
            return vo;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    //解析搜索结果
    private SearchResponseVo parseSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo vo = new SearchResponseVo();
        //1:总条数
        SearchHits hits = searchResponse.getHits();
        long totalHits = hits.getTotalHits();
        System.out.println("总条数：" + totalHits);
        vo.setTotal(totalHits);
        //2:商品集合
        SearchHit[] hits1 = hits.getHits();
        //商品集合
        List<Goods> goodsList = Arrays.stream(hits1).map(hit -> {
            //商品信息
            String sourceAsString = hit.getSourceAsString();
            Goods goods = JSONObject.parseObject(sourceAsString, Goods.class);
            //高亮
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if(null != highlightFields && highlightFields.size() > 0){
                HighlightField highlightField = highlightFields.get("title");
                String highlightTitle = highlightField.getFragments()[0].toString();
                goods.setTitle(highlightTitle);
            }
            return goods;
        }).collect(Collectors.toList());
        vo.setGoodsList(goodsList);
        //3、品牌集合
        ParsedLongTerms tmIdAgg = searchResponse.getAggregations().get("tmIdAgg");

        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            //品牌ID
            tmVo.setTmId(bucket.getKeyAsNumber().longValue());
            //品牌名称
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            tmVo.setTmName(tmNameAgg.getBuckets().get(0).getKeyAsString());
            //品牌的Logo
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            tmVo.setTmLogoUrl(tmLogoUrlAgg.getBuckets().get(0).getKeyAsString());
            return tmVo;
        }).collect(Collectors.toList());

        vo.setTrademarkList(trademarkList);

        //4、平台属性集合
        ParsedNested attrsAgg = searchResponse.getAggregations().get("attrsAgg");

        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");

        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //平台属性ID
            attrVo.setAttrId(bucket.getKeyAsNumber().longValue());
            //平台属性名称
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            //平台属性值集合
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            List<String> attrValueList = attrValueAgg.getBuckets().stream()
                    .map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            attrVo.setAttrValueList(attrValueList);
            return attrVo;
        }).collect(Collectors.toList());

        vo.setAttrsList(attrsList);
        return vo;
    }

    //构建搜索请求对象
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        //1:创建搜索请求对象  DSL: GET goods/_search  GET请求
        SearchRequest searchRequest = new SearchRequest();
        //2:构建搜索资源对象
        /**
         * {
         *
         * }
         */
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //组合条件的对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //2.1  查询关键词
        String keyword = searchParam.getKeyword();
        if(!StringUtils.isEmpty(keyword)){
            // query :  match 1）先分词  2）再等值匹配
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword)
                    .operator(Operator.AND));
            //"title": "我是中国人的手机    中国  国人 中国人  的  手机
            /**
             *  匹配的意义 ：   手机华为  手机 华为
             *  match:  select * from 表 where
             *                         (title == 中国 or title == 国人
             *                          or title == 中国人
             *                          or title == 的
             *                          or title == 手机)
             *                          and
             *                          (category1Id=2)
             *
             *    and:DSL语句中 must
             *    or:DSL语句中 should
             *    not:DSL语句中 must_not
             *
             *
             */
        }else{
            //没有关键词
            //查询所有
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }
        //2.2 查询 一二三级分类   精准查询    select * from 表 where categoryId = 61
        Long category1Id = searchParam.getCategory1Id();
        if(null != category1Id){
            // K : 域名   V: 域值  filter == must  and
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if(null != category2Id){
            // K : 域名   V: 域值  filter == must  and
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if(null != category3Id){
            // K : 域名   V: 域值  filter == must  and
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",category3Id));
        }
        //2.3: 品牌 精准查询    品牌ID:品牌名称
        String trademark = searchParam.getTrademark();
        if(!StringUtils.isEmpty(trademark)){
            String[] t = trademark.split(":"); // 0:id  1:名称
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",t[0]));
        }
        //2.4 :平台属性  超难
        //http://localhost:8090/path
        // ?keyword=手机&props=23:4G:运行内存&props=24:1200万:摄像头像素
        //Springmvc的课程了
        String[] props = searchParam.getProps();
        if(null != props && props.length > 0){
            //props=23:4G:运行内存   prop:平台属性ID:平台属性值:平台属性名称
            for (String prop : props) {
                //子组合对象
                BoolQueryBuilder subBoolQueryBuilder = QueryBuilders.boolQuery();
                String[] p = prop.split(":");
                if(p.length == 3){
                    //过滤查询
                    // 平台属性ID
                    subBoolQueryBuilder.must(
                            QueryBuilders.termQuery("attrs.attrId",p[0]));
                            //平台属性值
                    subBoolQueryBuilder.must(
                            QueryBuilders.termQuery("attrs.attrValue",p[1]));
                }
                //父组合对象 添加 子组合对象  and
                boolQueryBuilder.filter(QueryBuilders.
                        nestedQuery("attrs",subBoolQueryBuilder, ScoreMode.None));
            }

        }
        //将组合条件的对象放入到SearchSourceBuilder
        searchSourceBuilder.query(boolQueryBuilder);
        //3.分页
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        searchSourceBuilder.from((pageNo-1)*pageSize); //开始行
        searchSourceBuilder.size(pageSize); //每页数
        //4:排序   1:asc  1:desc  2:asc 2:desc ...
        String order = searchParam.getOrder();
        if(!StringUtils.isEmpty(order)){
            String[] o = order.split(":");//2  0: 1:
            String field = "";
            switch (o[0]){
                case "1" : field = "hotScore";break;
                case "2" : field = "price";break;
                case "3" : field = "createTime";break;
            }
            //ES 中排序
            searchSourceBuilder.sort(field,"asc".equalsIgnoreCase(o[1])?SortOrder.ASC:SortOrder.DESC);
        }else{
            //默认是按照 综合  由高到低
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }
        //5:高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title")
                        .preTags("<font style='color:red'>")
                        .postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);

        //6:品牌的集合  使用桶聚合查询
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("tmIdAgg").field("tmId").size(100)
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));
        //7:平台属性集合 使用桶聚合查询
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("attrsAgg","attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue")))
        );

        System.out.println("DSL:" + searchSourceBuilder.toString());

        //8：
        searchRequest.source(searchSourceBuilder);
        //指定索引库的名称   不指定查询所有索引库
        //searchRequest.indices("goods","haha","hehe");
        searchRequest.indices("goods");
        //指定type类型  ES7以上已经废弃了  可以不写了
        //searchRequest.types("info","haha","hehe");
        //返回搜索请求对象
        return searchRequest;
    }


}
